/*
 * Copyright 2012 Marsrutai.lt
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package lt.marsrutai.mobile.android.adapter;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import lt.marsrutai.mobile.android.AppContext;
import lt.marsrutai.mobile.android.R;
import lt.marsrutai.mobile.android.activity.intent.StopMapIntentHolder;
import lt.marsrutai.mobile.android.activity.intent.StopSearchIntentHolder;
import lt.marsrutai.mobile.android.bean.StopSearchCriteria;
import lt.marsrutai.mobile.android.content.StopSearchRecentSuggestionsProvider;
import lt.marsrutai.mobile.android.model.Schedule;
import lt.marsrutai.mobile.android.model.Stop;
import lt.marsrutai.mobile.android.model.StopBookmark;
import lt.marsrutai.mobile.android.util.BookmarkControlManager;
import lt.marsrutai.mobile.android.util.StringNormalizer;
import lt.marsrutai.mobile.android.util.StringUtils;
import lt.marsrutai.mobile.android.util.UnitConversionUtils;
import lt.marsrutai.mobile.android.widget.ListViewAdapter;
import android.app.Activity;
import android.os.Bundle;
import android.text.Spannable;
import android.text.style.BackgroundColorSpan;
import android.util.Pair;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.SectionIndexer;
import android.widget.TextView;

public class StopIndexedAdapter extends ListViewAdapter<Stop> implements SectionIndexer, Filterable {

    private Map<String, Integer> alphaIndexer;
    private String[] sections;
    private List<Stop> stops;

    private Bundle fragmentState;

    /**
     * Start and end positions of the matched text, for each filtered Stop
     */
    private Map<Stop, Pair<Integer, Integer>> matchPositions = new HashMap<Stop, Pair<Integer, Integer>>();

    /**
     * Caches inflated schedule numbers for a particular stop, these are later
     * re-used when cached stops are again shown on screen
     */
    private Map<Stop, List<TextView>> scheduleNumberCache = new HashMap<Stop, List<TextView>>();

    private StopSearchCriteria stopSearchCriteria;

    public StopIndexedAdapter(Activity activity, Bundle fragmentState) {
        super(activity, R.layout.stop_search_result_item);

        this.fragmentState = fragmentState;
    }

    public void setOriginalValues(List<Stop> stops) {
        this.stops = stops;//TODO
    }

    public void initIndexer() {
        alphaIndexer = new HashMap<String, Integer>();
        int count = getCount();

        for (int i = 0; i < count; i++) {
            Stop stop = getItem(i);

            String firstLetter = stop.getName().substring(0, 1).toUpperCase();

            if (!alphaIndexer.containsKey(firstLetter)) {
                alphaIndexer.put(firstLetter, i);
            }
        }

        ArrayList<String> sectionList = new ArrayList<String>(alphaIndexer.keySet());
        Collections.sort(sectionList, new Comparator<String>() {

            private final String alphabet = StopIndexedAdapter.this.activity.getString(R.string.alphabet);

            @Override
            public int compare(String letter1, String letter2) {
                return alphabet.indexOf(letter1) - alphabet.indexOf(letter2);
            }

        });

        sections = new String[sectionList.size()];
        sectionList.toArray(sections);
    }

    @Override
    public View doGetView(int position, View view, ViewGroup parent) {
        final Stop stop = getItem(position);
        final StopSearchIntentHolder intentHolder = new StopSearchIntentHolder(fragmentState);

        if (stop != null) {
            TextView stopName = findViewById(view, R.id.stop_name);
            if (stopName != null) {
                stopName.setText(stop.getName(), TextView.BufferType.SPANNABLE);

                // Highlight matched parts
                if (matchPositions.containsKey(stop)) {
                    Spannable wordtoSpan = (Spannable) stopName.getText();
                    Pair<Integer, Integer> pair = matchPositions.get(stop);
                    if (pair != null) {
                        wordtoSpan.setSpan(new BackgroundColorSpan(activity.getResources().getColor(R.color.textHighlightBackground)),
                                pair.first, pair.second, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                    }
                }
            }

            TextView directionName = (TextView) findViewById(view, R.id.direction_name);
            if (directionName != null) {
                directionName.setText(stop.getDirectionName());
            }

            ImageButton mapButton = (ImageButton) findViewById(view, R.id.map);
            mapButton.setFocusable(false);
            mapButton.setOnClickListener(new OnClickListener() {

                @Override
                public void onClick(View v) {
                    StopMapIntentHolder mapIntentHolder = new StopMapIntentHolder(activity);
                    mapIntentHolder.setScopeFolder(intentHolder.getScopeFolder());
                    mapIntentHolder.setStopId(stop.getId());
                    mapIntentHolder.setStops(Arrays.asList(new Stop[] {stop}));
                    mapIntentHolder.setTitle(stop.getName() + ", " + stop.getDirectionName());
                    activity.startActivity(mapIntentHolder.getIntent());
                }
            });

            final StopBookmark stopBookmark = new StopBookmark();
            stopBookmark.setScopeFolder(intentHolder.getScopeFolder());
            stopBookmark.setScopeName(intentHolder.getScopeName());
            stopBookmark.setStopId(stop.getId());
            stopBookmark.setStopName(stop.getName());
            stopBookmark.setStopDirectionName(stop.getDirectionName());

            BookmarkControlManager.setUpControls(view.findViewById(R.id.star_on), view.findViewById(R.id.star_off), stopBookmark);

            displaySchedules(view, stop);
        }
        return view;
    }

    private void displaySchedules(View view, Stop stop) {
        ViewGroup layout = findViewById(view, R.id.schedules);
        layout.removeAllViews();

// FIXME cached views appear and disappear when user interacts with the listview (http://code.google.com/p/marsrutai-lt-mobile/issues/detail?id=157)
//        // If schedules are cached, reuse them
//        List<TextView> cachedScheduleList = scheduleNumberCache.get(stop);
//        if (cachedScheduleList != null) {
//            for (TextView scheduleNumber : cachedScheduleList) {
//                ViewGroup parent = (ViewGroup) scheduleNumber.getParent();
//
//                // Remove all siblings from the parent view
//                if (parent != null && parent.getChildCount() > 0) {
//                    parent.removeAllViews();
//                }
//
//                layout.addView(scheduleNumber);
//            }
//        }
//        else {
            List<Schedule> schedules = stop.getSchedules();
            List<TextView> stopScheduleNames = new LinkedList<TextView>();
            scheduleNumberCache.put(stop, stopScheduleNames);

            if (schedules != null) {
                Set<String> addedScheduleNames = new HashSet<String>();
                for (Schedule schedule : schedules) {
                    String scheduleName = schedule.getName();

                    /*
                     * API sometimes returns multiple schedules with the same
                     * name, but different running days. Since the schedule has
                     * the same name, it should not be added multiple times to
                     * the schedule list
                     */
                    if (addedScheduleNames.contains(scheduleName)) {
                        continue;
                    }

                    TextView scheduleNumber = (TextView) inflater.inflate(R.layout.schedule_number_small, null);

                    scheduleNumber.setText(scheduleName);
                    scheduleNumber.setBackgroundColor(AppContext.getTransportInfoResolver().resolveTransportColor(new StopSearchIntentHolder(fragmentState).getScopeFolder(), schedule.getTransportId()));

                    LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
                            LinearLayout.LayoutParams.WRAP_CONTENT);
                    layoutParams.setMargins(0, 0, UnitConversionUtils.dpToPx(activity, 5), 0);
                    layout.addView(scheduleNumber, layoutParams);

                    // Cache schedule numbers for this stop
                    stopScheduleNames.add(scheduleNumber);

                    // Store this name, so that it won't be duplicated
                    addedScheduleNames.add(scheduleName);
                }
//            }
        }
    }

    @Override
    public int getPositionForSection(int section) {
        return alphaIndexer.get(sections[section]);
    }

    @Override
    public int getSectionForPosition(int position) {
        return 1;
    }

    @Override
    public Object[] getSections() {
        return sections;
    }

    @Override
    public Filter getFilter() {
        return new Filter() {

            @SuppressWarnings("unchecked")
            @Override
            protected void publishResults(CharSequence constraint, FilterResults results) {
                StopIndexedAdapter.this.clear();
                for (Stop stop : (Collection<Stop>) results.values) {
                    StopIndexedAdapter.this.add(stop);
                }
            }

            @Override
            protected FilterResults performFiltering(CharSequence constraint) {
                List<Stop> filteredResults = getFilteredResults(constraint != null ? constraint.toString() : null);

                FilterResults results = new FilterResults();
                results.values = filteredResults;

                return results;
            }

            private List<Stop> getFilteredResults(String constraint) {
                List<Stop> filtered = new ArrayList<Stop>();
                matchPositions.clear();

                constraint = stopSearchCriteria.getQuery();
                if (!StringUtils.equals(constraint, StopSearchRecentSuggestionsProvider.QUERY_CLOSEST_STOPS)) {
                    constraint = StringNormalizer.normalize(constraint.toString().toLowerCase());
                }
                else {
                    constraint = null;
                }

                for (Stop stop : stops) {
                    String stopName = StringNormalizer.normalize(stop.getName().toLowerCase());

                    filtered.add(stop);

                    if (null != constraint && stopName.contains(constraint)) {
                        // Store positions of the match
                        Integer matchStart = stopName.indexOf(constraint);
                        Integer matchEnd = matchStart + constraint.length();
                        matchPositions.put(stop, new Pair<Integer, Integer>(matchStart, matchEnd));
                    }
                }

                return filtered;
            }
        };

    }

    public void setSearchCriteria(StopSearchCriteria stopSearchCriteria) {
        this.stopSearchCriteria = stopSearchCriteria;
    }
}
